package com.qst.yunpan.service.impl;

import com.qst.yunpan.dao.FileDao;
import com.qst.yunpan.dao.OfficeDao;
import com.qst.yunpan.dao.UserDao;
import com.qst.yunpan.pojo.FileCustom;
import com.qst.yunpan.pojo.RecycleFile;
import com.qst.yunpan.pojo.SummaryFile;
import com.qst.yunpan.pojo.User;
import com.qst.yunpan.service.FileService;
import com.qst.yunpan.utils.FileUtils;
import com.qst.yunpan.utils.UserUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
public class FileServiceImpl implements FileService {
    // 文件相对前缀
    public static final String PREFIX = "WEB-INF" + File.separator + "file" + File.separator;
    // 新用户注册默认文件夹
    public static final String[] DEFAULT_DIRECTORY = {"vido", "music", "source", "image", User.RECYCLE};
    @Autowired
    private UserDao userDao;
    @Autowired
    private OfficeDao officeDao;
    @Autowired
    private FileDao fileDao;

    // 新建对应的文件夹
    @Override
    public void addNewNameSpace(HttpServletRequest request, String nameSpace) {
        String filename = getRootPath(request);
        File file = new File(filename, nameSpace);
        file.mkdirs();
        for (String newFileName : DEFAULT_DIRECTORY) {
            File newFile = new File(file, newFileName);
            newFile.mkdirs();
        }
    }

    // 获取根路径
    public String getRootPath(HttpServletRequest request) {
        String rootPath = request.getSession().getServletContext().getRealPath("/") + PREFIX;
        return rootPath;
    }

    // 获取文件名
    public String getFileName(HttpServletRequest request, String fileName) {
        if (fileName == null || fileName.equals("\\")) {
            // System.out.println(1);
            fileName = "";
        }
        // 处理文件路径
        fileName = fileName.replace("\\", "//");
        // 加入用户名
        String username = UserUtils.getUsername(request);
        String realpath = getRootPath(request) + username + File.separator + fileName;
        return realpath;
    }

    public String getFileName(HttpServletRequest request, String fileName, String username) {
        // 分别判断用户和文件名
        if (username == null) {
            return getFileName(request, fileName);
        }
        if (fileName == null) {
            fileName = "";
        }
        return getRootPath(request) + username + File.separator + fileName;
    }

    // 得到文件list
    @Override
    public List<FileCustom> listFile(String realPath) {
        //获取路径下所有的文件信息
        File[] files = new File(realPath).listFiles();
        List<FileCustom> lists = new ArrayList<FileCustom>();
        if (files != null) {
            for (File file : files) {
                // 排除回收站
                if (!file.getName().equals(User.RECYCLE)) {
                    // 创建FileCustom对象并添加到列表中
                    FileCustom custom = new FileCustom();
                    custom.setFileName(file.getName());
                    custom.setLastTime(FileUtils.formatTime(file.lastModified()));
                    custom.setCurrentPath(realPath);
                    if (file.isDirectory()) {
                        custom.setFileSize("-");
                    } else {
                        custom.setFileSize(FileUtils.getDataSize(file.length()));
                    }
                    custom.setFileType(FileUtils.getFileType(file));
                    lists.add(custom);
                }
            }
        }
        return lists;
    }

    // 上传文件
    public void uploadFilePath(HttpServletRequest request, MultipartFile[] files, String currentPath) throws Exception {
        for (MultipartFile file : files) {
            // 文件会保存至tomcat下，不在本项目中 可以
            // TODO
//            System.out.println("111: " + currentPath);
            String fileName = file.getOriginalFilename();
            String filePath = getFileName(request, currentPath);
//            System.out.println("111: " + fileName);
//            System.out.println("111: " + filePath);
            File distFile = new File(filePath, fileName);

            if (!distFile.exists()) {
                file.transferTo(distFile);
                // 对于office文件(如docx、xlsx等格式)
                // 需借助于百度云盘进行保存,以便后期直接在线阅览此类文档
                if ("office".equals(FileUtils.getFileType(distFile))) {
                    try {
                        // 获取后缀名
                        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
                        // 获取文档ID
                        String documentId = FileUtils.getDocClient().createDocument(distFile, fileName, suffix).getDocumentId();
                        officeDao.addOffice(documentId, FileUtils.MD5(distFile));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        reSize(request);
    }

    // 重新计算用户文件空间大小
    public void reSize(HttpServletRequest request) {
        String userName = UserUtils.getUsername(request);
        try {
            userDao.reSize(userName, countFileSize(request));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 计算文件大小
    public String countFileSize(HttpServletRequest request) {
        long countFileSize = countFileSize(new File(getFileName(request, null)));
        return FileUtils.getDataSize(countFileSize);
    }

    private long countFileSize(File srcFile) {
        File[] listFiles = srcFile.listFiles();
        if (listFiles == null) {
            return 0;
        }
        long count = 0;
        // 递归计算文件夹大小
        for (File file : listFiles) {
            if (file.isDirectory()) {
                count += countFileSize(file);
            } else {
                count += file.length();
            }
        }
        return count;
    }

    // 单文件直接下载 多文件打包下载
    @Override
    public File downPackage(HttpServletRequest request, String currentPath, String[] fileNames, String username) throws Exception {
        File downLoadFile = null;
        if (currentPath == null) {
            currentPath = "";
        }
        // 单文件下载
        if (fileNames.length == 1) {
            downLoadFile = new File(getFileName(request, currentPath, username), fileNames[0]);
            if (downLoadFile.isFile()) return downLoadFile;
        }
        // 多文件打包下载
        String[] sourcePath = new String[fileNames.length];
        for (int i = 0; i < fileNames.length; i++) {
            sourcePath[i] = getFileName(request, currentPath, username) + File.separator + fileNames[i];
        }
        String packageZipName = packageZip(sourcePath);
        downLoadFile = new File(packageZipName);
        return downLoadFile;
    }

    // 删除打包文件
    @Override
    public void deleteDownPackage(File downLoadFile) throws Exception {
        if (downLoadFile.getName().endsWith("个文件.zip")) {
            downLoadFile.delete();
        }
    }

    // 新增目录
    @Override
    public boolean addDirectory(HttpServletRequest request, String currentPath, String directoryName) {
        File file = new File(getFileName(request, currentPath), directoryName);
        return file.mkdir();
    }

    // 多文件打包为zip
    private String packageZip(String[] sourcePath) throws Exception {
        // 生成zip文件名，如果只有一个文件，则直接使用该文件名，否则添加“等n个文件”
        String zipName = sourcePath[0] + (sourcePath.length == 1 ? "" : "等" + sourcePath.length + "个文件") + ".zip";
        ZipOutputStream zos = null;
        try {
            // 创建ZipOutputStream对象，用于写入zip文件
            zos = new ZipOutputStream(new FileOutputStream(zipName));
            // 遍历源文件路径数组，将每个文件或文件夹添加到zip中
            for (String string : sourcePath) {
                writeZos(new File(string), "", zos);
            }
        } finally {
            // 关闭ZipOutputStream
            if (zos != null) {
                zos.close();
            }
        }
        // 返回生成的zip文件名
        return zipName;
    }

    // 递归地将文件或文件夹写入ZipOutputStream
    private void writeZos(File file, String basePath, ZipOutputStream zos) throws IOException {
        // 检查文件是否存在
        if (!file.exists()) {
            throw new FileNotFoundException();
        }
        // 如果是文件夹
        if (file.isDirectory()) {
            File[] listFiles = file.listFiles();
            // 如果文件夹不为空，递归处理每个子文件或子文件夹
            if (listFiles.length != 0) {
                for (File childFile : listFiles) {
                    writeZos(childFile, basePath + file.getName() + File.separator, zos);
                }
            }
        } else {
            // 如果是文件，将其写入zip
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            // 创建ZipEntry，指定文件在zip中的路径
            ZipEntry entry = new ZipEntry(basePath + file.getName());
            zos.putNextEntry(entry);
            int count = 0;
            byte data[] = new byte[1024];
            // 读取文件内容并写入zip
            while ((count = bis.read(data)) != -1) {
                zos.write(data, 0, count);
            }
            // 关闭输入流
            bis.close();
        }
    }

    // 文件分类模块
    public List<FileCustom> searchFile(HttpServletRequest request, String currentPath, String reg, String regType) {
        List<FileCustom> list = new ArrayList<>();
        matchFile(request, list, new File(getSearchFileName(request, currentPath)), reg, regType == null ? "" : regType);
        return list;
    }

    // 删除
    @Override
    public void delDirectory(HttpServletRequest request, String currentPath, String[] directoryName) throws Exception {
        for (String fileName : directoryName) {
            // 拼接源文件的地址
            String srcPath = currentPath + File.separator + fileName;
            // 根据源文件相对地址拼接 绝对路径
            File src = new File(getFileName(request, srcPath));// 即将删除的文件地址
            File dest = new File(getRecyclePath(request));// 回收站目录地址
            // 调用commons.jar包中的moveToDirectory移动文件,移至回收站目录
            org.apache.commons.io.FileUtils.moveToDirectory(src, dest, true);
            // 保存本条删除信息
            fileDao.insertFiles(srcPath, UserUtils.getUsername(request));
        }
        //重新计算文件大小
        reSize(request);
    }

    // 获取回收站目录地址
    public String getRecyclePath(HttpServletRequest request) {
        return getFileName(request, User.RECYCLE);
    }

    private String getSearchFileName(HttpServletRequest request, String fileName) {
        if (fileName == null || fileName.equals("\\")) {
            // System.out.println(1);
            fileName = "";
        }
        String username = UserUtils.getUsername(request);
        String realpath = getRootPath(request) + username + File.separator + fileName;
        return realpath;
    }

    // 查找文件并分类
    private void matchFile(HttpServletRequest request, List<FileCustom> list, File dirFile,
                           String reg,
                           String regType) {
        File[] listFiles = dirFile.listFiles();
        if (listFiles != null) {
            for (File file : listFiles) {
                if (file.isFile()) {
                    // 通过FileUtils获取文件分类
                    String suffixType = FileUtils.getFileType(file);
                    if (suffixType.equals(regType) || (reg != null && file.getName().contains(reg))) {
                        FileCustom custom = new FileCustom();
                        custom.setFileName(file.getName());
                        custom.setLastTime(FileUtils.formatTime(file.lastModified()));
                        String parentPath = file.getParent();
                        String prePath = parentPath.substring(
                                parentPath.indexOf(getSearchFileName(request, null)) + getSearchFileName(request, null).length());
                        custom.setCurrentPath(File.separator + prePath);
                        if (file.isDirectory()) {
                            custom.setFileSize("-");
                        } else {
                            custom.setFileSize(FileUtils.getDataSize(file.length()));
                        }
                        custom.setFileType(FileUtils.getFileType(file));
                        list.add(custom);
                    }
                } else {
                    matchFile(request, list, file, reg, regType);
                }
            }
        }
    }

    @Override
    public Boolean renameDirectory(HttpServletRequest request, String currentPath, String srcName, String destName) {
        // 根据源文件名获取源地址
        File file = new File(getFileName(request, currentPath), srcName);
        // 建立新文件
        File destFile = new File(getFileName(request, currentPath), destName);
        return file.renameTo(destFile);
    }

    @Override
    public void moveDirectory(HttpServletRequest request, String currentPath, String[] directoryName, String targetdirectorypath) throws Exception {
        for (String srcName : directoryName) {
            // 建立源文件
            File srcFile = new File(getFileName(request, currentPath), srcName);
            // 建立目标文件
            File targetFile = new File(getFileName(request, targetdirectorypath), srcName);

            // 处理目标目录中存在同名问题
            String srcname = srcName;
            String prefixname = "";
            String targetname = "";
            if (targetFile.exists()) {

                // 分割文件名，文件名格式为 "(数字)文件名"
                String[] srcnamesplit = srcname.split("\\)");

                if (srcnamesplit.length > 1) {
                    // 提取文件名中的数字部分
                    String intstring = srcnamesplit[0].substring(1);

                    // 使用正则表达式判断提取的部分是否为数字
                    Pattern pattern = Pattern.compile("[0-9]*");
                    Matcher isNum = pattern.matcher(intstring);

                    // 如果提取的部分是数字，去掉数字部分，保留文件名
                    if (isNum.matches()) {
                        srcname = srcname.substring(srcnamesplit[0].length() + 1);
                    }
                }

                // 循环生成新的文件名，避免文件名冲突
                for (int i = 1; true; i++) {
                    // 生成新的文件名前缀，格式为 "(数字)"
                    prefixname = "(" + i + ")";
                    targetname = prefixname + srcname;

                    // 创建新的目标文件对象
                    targetFile = new File(targetFile.getParent(), targetname);

                    // 检查新生成的文件名是否存在，如果不存在则跳出循环
                    if (!targetFile.exists()) {
                        break;
                    }
                }
                // 确定目标文件名
                targetFile = new File(targetFile.getParent(), targetname);
            }

            // 移动: 先复制 再删除
            copyfile(srcFile, targetFile);
            delFile(srcFile);
        }
    }

    // 删除文件、文件夹
    private void delFile(File srcFile) throws Exception {
        // 文件直接删除
        if (!srcFile.isDirectory()) {
            /* 使用map 存储删除的 文件路径，同时保存用户名 */
            srcFile.delete();
            return;
        }
        // 文件夹递归删除
        File[] listFiles = srcFile.listFiles();
        for (File file : listFiles) {
            if (file.isDirectory()) {
                delFile(file);
            } else {
                if (file.exists()) {
                    file.delete();
                }
            }
        }
        if (srcFile.exists()) {
            srcFile.delete();
        }
    }

    //获取文件路径
    @Override
    public SummaryFile summarylistFile(String realPath, int number) {
        File file = new File(realPath);
        SummaryFile sF = new SummaryFile();
        List<SummaryFile> returnlist = new ArrayList<SummaryFile>();
        if (file.isDirectory()) {
            sF.setisFile(false);
            // 位于根目录,增加 yun盘 的显示
            if (realPath.length() <= number) {
                sF.setFileName("yun盘");
                sF.setPath("");
            } else {
                String path = file.getPath();
                sF.setFileName(file.getName());
                // 截取固定长度 的字符串，从number开始到value.length-number结束.
                sF.setPath(path.substring(number));
            }

            // 递归获取当前文件夹下的文件/文件夹
            for (File filex : file.listFiles()) {
                //获取当前文件的路径，构造该文件
                SummaryFile innersF = summarylistFile(filex.getPath(), number);
                if (!innersF.getisFile()) {
                    returnlist.add(innersF);
                }
            }
            sF.setListFile(returnlist);
            // 设置文件夹的包含文件夹个数
            sF.setListdiretory(returnlist.size());
        } else {
            sF.setisFile(true);
        }
        return sF;
    }

    // 递归实现复制功能
    @Override
    public void copyDirectory(HttpServletRequest request, String currentPath, String[] directoryName, String targetdirectorypath) throws Exception {
        for (String srcName : directoryName) {
            // 建立源文件
            File srcFile = new File(getFileName(request, currentPath), srcName);
            // 建立目标文件
            File targetFile = new File(getFileName(request, targetdirectorypath), srcName);

            // 处理目标目录中存在同名问题
            String srcname = srcName;
            String prefixname = "";
            String targetname = "";
            if (targetFile.exists()) {

                // 分割文件名，文件名格式为 "(数字)文件名"
                String[] srcnamesplit = srcname.split("\\)");

                if (srcnamesplit.length > 1) {
                    // 提取文件名中的数字部分
                    String intstring = srcnamesplit[0].substring(1);

                    // 使用正则表达式判断提取的部分是否为数字
                    Pattern pattern = Pattern.compile("[0-9]*");
                    Matcher isNum = pattern.matcher(intstring);

                    // 如果提取的部分是数字，去掉数字部分，保留文件名
                    if (isNum.matches()) {
                        srcname = srcname.substring(srcnamesplit[0].length() + 1);
                    }
                }

                // 循环生成新的文件名，避免文件名冲突
                for (int i = 1; true; i++) {
                    // 生成新的文件名前缀，格式为 "(数字)"
                    prefixname = "(" + i + ")";
                    targetname = prefixname + srcname;

                    // 创建新的目标文件对象
                    targetFile = new File(targetFile.getParent(), targetname);

                    // 检查新生成的文件名是否存在，如果不存在则跳出循环
                    if (!targetFile.exists()) {
                        break;
                    }
                }
                // 确定目标文件名
                targetFile = new File(targetFile.getParent(), targetname);
            }

            // 复制
            copyfile(srcFile, targetFile);
        }
    }

    // copy功能实现
    private void copyfile(File srcFile, File targetFile) throws IOException {
        if (!srcFile.isDirectory()) {
            // 如果是文件，直接复制

            // 创建目标文件 targetFile，如果文件已存在则覆盖
            targetFile.createNewFile();

            // 创建一个 FileInputStream 对象 src，用于读取源文件 srcFile
            FileInputStream src = new FileInputStream(srcFile);

            // 创建一个 FileOutputStream 对象 target，用于写入目标文件 targetFile
            FileOutputStream target = new FileOutputStream(targetFile);

            // 获取源文件的文件通道
            FileChannel in = src.getChannel();
            // 获取目标文件的文件通道
            FileChannel out = target.getChannel();

            // 使用文件通道的 transferTo 方法，将源文件的内容从位置 0 开始，复制到目标文件中，复制的长度为源文件的大小
            in.transferTo(0, in.size(), out);

            // 关闭流
            src.close();
            target.close();
        } else {
            // 如果是文件夹，递归复制
            File[] listFiles = srcFile.listFiles();
            targetFile.mkdir();
            for (File file : listFiles) {
                File realtargetFile = new File(targetFile, file.getName());
                copyfile(file, realtargetFile);
            }
        }
    }

    // 回收站显示所有删除文件
    public List<RecycleFile> recycleFiles(HttpServletRequest request) throws Exception {
        List<RecycleFile> recycleFiles = fileDao.selectFiles(UserUtils.getUsername(request));

        for (RecycleFile file : recycleFiles) {
            File f = new File(getRecyclePath(request), new File(file.getFilePath()).getName());
            file.setFileName(f.getName());
            file.setLastTime(FileUtils.formatTime(f.lastModified()));
        }
        return recycleFiles;
    }

    // 还原文件
    public void revertDirectory(HttpServletRequest request, int[] fileId) throws Exception {
        // 遍历
        for (int id : fileId) {
            // 获取文件
            RecycleFile file = fileDao.selectFile(id);
            String fileName = new File(file.getFilePath()).getName();
            File src = new File(getRecyclePath(request), fileName);
            File dest = new File(getFileName(request, file.getFilePath()));
            // 将文件从回收站目录移动到目标目录
            org.apache.commons.io.FileUtils.moveToDirectory(src, dest.getParentFile(), true);
            // 删除回收记录
            fileDao.deleteFile(id, UserUtils.getUsername(request));
        }
    }

    // 清空回收站
    public void delAllRecycle(HttpServletRequest request) throws Exception {
        // 获取回收站中的所有文件
        File file = new File(getRecyclePath(request));
        // 遍历文件夹下所有文件
        File[] inferiorFile = file.listFiles();
        for (File f : inferiorFile) {
            delFile(f);
        }
        // 根据用户删除回收记录
        fileDao.deleteFiles(UserUtils.getUsername(request));
        reSize(request);
    }
}